﻿using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Collections.Generic;
using System.Threading.Tasks;
using IBoLT.Fractals;

namespace IBoLT.Fractals
{
	public class FractalTree
	{
		// Размер генерируемой текстуры
		private int tWidth = 300;
		private int tHeight = 300;

		// Оригинальная текстура
		private Bitmap original;
		// Текстура для создания объекта Graphics
		private Bitmap bm;
		private Graphics gr;

		public Int32[] palette = {-8372160, -16760832, -16711936};
		
		// Кисти для окрашивания ветвей ( 0 - длинные стволы; 1 - средние ветви; 2 - листья)
		private Pen[] pbr = { new Pen(Color.FromArgb(-8372160)), new Pen(Color.FromArgb(-16760832)), new Pen(Color.FromArgb(-16711936)) };
		
		// Точка ветвления на стволе (в процентах ко всей длине ствола)
		public int bp;
		// Угол между стволом и ветвью (в градусах)
		public int ba;
		// Длина ветвей (в процентах к длине ствола от точки ветвления до конца)
		public int bl;

		// Допустимое отклонение значения переменной bp (в процентах ко всей длине ствола)
		public int bpDev;
		// Допустимое отклонение значения переменной ba (в градусах)
		public int baDev;
		// Допустимое отклонение значения переменной bl (в процентах к длине ствола от точки ветвления до конца)
		public int blDev;

		// Пятая часть длины главного ствола (используется для окрашивания)
		private int h15;
		// Глубина ветвления (количество итераций)
		public int depth;

		public Bitmap Texture
		{
			get { return original; }
			set
			{
				original = value;
				tWidth = original.Width;
				tHeight = original.Height;
			}
		}


		public FractalTree()
		{

		}

		public void SetColor(int color, int index)
		{
			palette[index] = color;
			pbr[index] = new Pen(Color.FromArgb(color));
		}

		public System.Drawing.Bitmap ToBitmap()
		{
			bm = original;
			gr = Graphics.FromImage(bm);

			// Координаты главного ствола
			int x = tWidth / 2;
			int y1 = (int)(tHeight*.90);
			int y2 = (int)(tHeight*.10);

			h15 = Math.Abs(tHeight) / 5;
			Fractal(new Line(x, y1, x, y2), depth);

			return bm;
		}

		private void Fractal(Line l, int depth)
		{
			// Длина ветви
			int len = l.Length;
			
			// Окраска листьев
			if (len < 3)
			{
				gr.DrawLine(pbr[2], l.StartPoint, l.EndPoint);
			}
			// Окраска средних ветвей
			else if (len < h15)
			{
				gr.DrawLine(pbr[1], l.StartPoint, l.EndPoint);
			}
			// Окраска длинных стволов
			else
			{
				gr.DrawLine(pbr[0], l.StartPoint, l.EndPoint);
			}

			while ((depth != 0) && (len > 1))
			{
				// Получаем второй сегмент линии после деления в точке ветвления
				Line l2 = l.SplitAt(bp)[1];

				if (l2.Length == l.Length)
				{
					return;
				}

				// В качестве заготовок левой и правой ветвей беру второй сегмент линии с учётом отклонений
				Line bp1 = l.SplitAt(bp + MathEx.Dev(bpDev))[1];
				Line bp2 = l.SplitAt(bp + MathEx.Dev(bpDev))[1];

				// В качестве основной линии беру второй сегмент начальной
				l = l2;

				// Нахожу линии с учётом угла ветвления, длины ветвей и допустимых откланений
				Line lineLeft = bp1.Branch(360 - ba + MathEx.Dev(baDev), (int)(bp1.Length * bl / 100 + MathEx.Dev(blDev)));
				Line lineRight = bp2.Branch(ba + MathEx.Dev(baDev), (int)(bp2.Length * bl / 100 + MathEx.Dev(blDev)));

				// Вызываю данный метод для обеих полученных линий
				Fractal(lineLeft, depth - 1);
				Fractal(lineRight, depth - 1);
			}
		}
	}

	public class Line
	{
		protected Point sp;
		protected Point ep;

		public Line()
		{
			sp=new Point(0, 0);
			ep=new Point(0, 0);
		}

		public Line(Point startPoint, Point endPoint)
		{
			sp = startPoint;
			ep = endPoint;
		}

		public Line(int x1, int y1, int x2, int y2)
		{
			sp = new Point(x1, y1);
			ep = new Point(x2, y2);
		}

		// Разбивает линию на две части в точке, определяемой параметром precent (процент)
		public Line[] SplitAt(int percent)
		{
			Line[] l = new Line[2];
			Size s = Size;
			l[0] = new Line(sp, new Point((int)(sp.X + s.Width * percent / 100), (int)(sp.Y + s.Height * percent / 100)));
			l[1] = new Line(new Point((int)(sp.X + s.Width * percent / 100), (int)(sp.Y + s.Height * percent / 100)), ep);
			return l;
		}

		public Line Branch(int angle, int length)
		{
			double rangle = angle * Math.PI / 180.0;
			double a = Math.Atan2(Size.Height, Size.Width);
			a = a + rangle;
			return new Line(sp, new Point((int)(sp.X + length * Math.Cos(a)), (int)(sp.Y + length * Math.Sin(a))));
		}

		public Point StartPoint
		{
			get {return sp;}
			set {sp = value;}
		}

		public Point EndPoint
		{
			get {return ep;}
			set {ep = value;}
		}

		public int Length
		{
			get {return (int)Math.Sqrt((ep.X - sp.X) *  (ep.X - sp.X) + (ep.Y - sp.Y) * (ep.Y - sp.Y));}
		}

		public Size Size
		{
			get {return new Size(ep.X - sp.X, ep.Y - sp.Y);}
		}
	}

	public class MathEx
	{
		static Random rnd=new Random();

		static public int Dev(int r)
		{
			return rnd.Next(-r, r);
		}
	}
}